package com.seednia.module.core.xport;

import java.beans.PropertyDescriptor;
import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Map;

import javax.xml.bind.JAXBException;
import javax.xml.bind.annotation.XmlTransient;

import org.apache.commons.beanutils.PropertyUtils;

import au.com.bytecode.opencsv.CSVReader;

import com.seednia.foundation.framework.dto.AbstractRecord;
import com.seednia.foundation.framework.service.IDataOrientedService;
import com.seednia.foundation.framework.service.ServiceContainer;

/**
 * 
 * @author Young Suk Ahn
 *
 */
public class ImporterCsv extends AbstractImporter  {
	
	public static final String FORMAT = "csv";
	
	public static final String NULL_STRING = "__NULL__";
	
	SimpleDateFormat sdf = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss");
	
	public ImporterCsv()
	{
		super.setFormat(FORMAT);
	}
	
	/**
	 * Jaxb Unmarshalling
	 * @param is
	 * @return
	 * @throws JAXBException
	 */
	public XportDataContainer importFrom(InputStream is, Map<String, Object> params) throws Exception
	{
		BufferedReader reader = new BufferedReader(new InputStreamReader(is));
		CSVReader csvReader = new CSVReader(reader);
		int lineNum = 0;
	    String [] nextLine;
	    String [] header;   
		
		XportDataContainer retval = new XportDataContainer();
		List<AbstractRecord> records = new ArrayList<AbstractRecord>(); 
		XportData xportData = new XportData();
		xportData.setRecords(records);
		retval.addXportData(xportData);
	
		
	    // First line is comment
	    lineNum++;
	    if ((nextLine = csvReader.readNext()) != null)
	    	;
	    else throw new XportException("Meta header line not found");

	    // Second line is header with each of the field names
	    lineNum++;
	    if ((nextLine = csvReader.readNext()) != null) {
	    	if (nextLine.length > 0 && nextLine[0] != null || nextLine[0].trim().length() > 0)
	    		xportData.setProcessorServiceId(nextLine[0]);
	    	if (nextLine.length > 1 && nextLine[1] != null || nextLine[1].trim().length() > 0)
	    		retval.setExportDate( (Date)objectFromString(nextLine[1], Date.class) );
	    	if (nextLine.length > 2 && nextLine[2] != null || nextLine[2].trim().length() > 0)
	    		retval.setComments(nextLine[2]);
	    }
	    else throw new XportException("Meta data line not found");
		IDataOrientedService dos = (IDataOrientedService)ServiceContainer.getInstance().get(xportData.getProcessorServiceId());

	    // Third line is header with each of the field names
	    lineNum++;
	    if ((nextLine = csvReader.readNext()) != null)
	    	header = nextLine.clone();
	    else throw new XportException("Header line not found");
	    
	    validateHeader(header, dos);

	
	    // Subsequent lines are the actual data
	    while ((nextLine = csvReader.readNext()) != null) {
	    	try {
	    		lineNum++;
		    	AbstractRecord record = processLineIn(header, nextLine, dos);
				
		    	records.add(record);
	    	} catch (Exception e) {
	    		throw new XportException("Exception at line "+lineNum+": "+e.getLocalizedMessage(), e);
	    	}
	    }

	    return retval;
	}
	
	/**
	 * returns an array of string this is a list of property names that are not transient
	 * @param record
	 * @return
	 * @throws IllegalStateException
	 * @throws InstantiationException
	 * @throws IllegalAccessException
	 * @throws InvocationTargetException
	 * @throws NoSuchMethodException
	 */
	protected String[] obtainHeader(IDataOrientedService dos) throws IllegalStateException, InstantiationException, IllegalAccessException, InvocationTargetException, NoSuchMethodException
	{
		ArrayList<String> header = new ArrayList<String>() ;
		PropertyDescriptor propDescrs[] = PropertyUtils.getPropertyDescriptors(dos.getRecordClass());
		for (PropertyDescriptor propDescr: propDescrs) {
			Method writeMethod = propDescr.getWriteMethod();
			if (writeMethod != null) {
				XmlTransient transientAnnot = writeMethod.getAnnotation(XmlTransient.class);
				if (transientAnnot == null) {
					header.add(propDescr.getDisplayName());
				}
			}
		}
		return header.toArray(new String[header.size()]);
	}
	
	protected String[] processLineOut(String[] header, AbstractRecord record) throws IllegalStateException, InstantiationException, IllegalAccessException, InvocationTargetException, NoSuchMethodException
	{
		String line[] = new String[header.length];
		int index = 0;
		for (String propName: header) {
			line[index++] = stringFromObject(PropertyUtils.getProperty(record, propName));
		}
		return line;
	}
	
	/**
	 * Process a input CSV line
	 * @param header
	 * @param line
	 * @param dos
	 * @return
	 * @throws IllegalStateException
	 * @throws InstantiationException
	 * @throws IllegalAccessException
	 * @throws InvocationTargetException
	 * @throws NoSuchMethodException
	 */
	protected AbstractRecord processLineIn(String [] header, String [] line, IDataOrientedService dos) throws IllegalStateException, InstantiationException, IllegalAccessException, InvocationTargetException, NoSuchMethodException
	{
		AbstractRecord record = dos.createRecordInstance();
		for (int i=0; i < header.length; i++) {
			Class<?> propType = PropertyUtils.getPropertyType(record, header[i] );
			Object value = objectFromString(line[i], propType);
			PropertyUtils.setProperty(record, header[i], value);
		}
		return record;
	}
	

	/**
	 * Checks whether the header line is valid compared to the propertyBeans
	 * @param header
	 * @param dos
	 * @throws IllegalArgumentException
	 * @throws InstantiationException
	 * @throws IllegalAccessException
	 */
	protected void validateHeader(String [] header, IDataOrientedService dos) throws IllegalArgumentException, InstantiationException, IllegalAccessException
	{
		AbstractRecord dummy = dos.createRecordInstance();
		for (String propName: header) {
			if ( !PropertyUtils.isWriteable(dummy, propName))
				throw new IllegalArgumentException("There is no writtable property "+propName+" in class "+dos.getRecordClass().getName());
		}
	}

	/**
	 * Transforms the string value to object based on the type
	 * @param strVal
	 * @param type
	 * @return
	 */
	protected Object objectFromString(String strVal, Class<?> type)
	{
		Object retval = null;
		if (strVal.equals(NULL_STRING))
			return null;
		if (type == String.class)
		{
			retval = strVal;
		}
		else if (type == int.class || type == Integer.class)
		{
			retval = Integer.parseInt(strVal);
		}
		else if (type == double.class || type == Double.class)
		{
			retval = Double.parseDouble(strVal);
		}
		else if (type == Date.class)
		{
			try {
				retval = sdf.parse(strVal);
			} catch (ParseException e) {
				throw new RuntimeException(e);
			}
		}
		else if (type == float.class || type == Float.class)
		{
			retval = Float.parseFloat(strVal);
		}
		else if (type == short.class || type == Short.class)
		{
			retval = Short.parseShort(strVal);
		}
		else if (type == boolean.class || type == Boolean.class)
		{
			retval = Boolean.parseBoolean(strVal);
		}
		return retval;
	}
	
	protected String stringFromObject(Object val)
	{
		if (val == null)
			return NULL_STRING;
		String retval = val.toString();
		if (val instanceof Date) {
			retval = sdf.format(val);
		}
		return retval;
	}
}
